View Javadoc

1   /*
2    * J.A.D.E. Java(TM) Addition to Default Environment.
3    * Latest release available at http://jade.dautelle.com/
4    * This class is public domain (not copyrighted).
5    */
6   package ch.twiddlefinger.inet.rewinder.model.parser.conversion;
7   
8   import java.io.InvalidObjectException;
9   import java.io.ObjectStreamException;
10  import java.io.Serializable;
11  
12  import java.util.Collection;
13  import java.util.Collections;
14  
15  
16  /***
17   * <p> This class represents the base class for all enumerates; its semantic
18   *      is somewhat similar to the new JDK Tiger <code>enum</code> base type, 
19   *      with some additional capabilities. In particular:<ul>
20   *     <li> The enumeration is extendable (if the constructor is not private).
21   *          For example:<pre>
22   *     public class Event {
23   *         public static abstract class BaseId extends Enum {
24   *             public static final Collection VALUES = getInstances(BaseId.class);
25   *         }
26   *     }
27   *     public class MouseEvent extends Event {
28   *         public static class Id extends BaseId {
29   *             public static final Collection VALUES = getInstances(Id.class);
30   *             public static final Id MOUSE_CLICKED = new Id();
31   *             public static final Id MOUSE_PRESSED = new Id();
32   *             public static final Id MOUSE_RELEASED = new Id();
33   *         }
34   *     }
35   *     public class KeyEvent extends Event {
36   *         public static class Id extends BaseId {
37   *             public static final Collection VALUES = getInstances(Id.class);
38   *             public static final Id KEY_PRESSED = new Id();
39   *             public static final Id KEY_RELEASED = new Id();
40   *         }
41   *     }</pre></li>
42   *     <li> {@link Enum}'s instances can be mapped to custom values (up to 
43   *          <code>long</code>) for use in <code>switch</code> statements,
44   *         bit-wise sets (values power of 2) or look-up tables.
45   *         Duplications (name or value) would result in exception thrown 
46   *         during initialization:
47   *     <pre>
48   *     public static final class Color extends Enum {
49   *         public static final Collection VALUES = getInstances(Color.class);
50   *         private Color(int value, String name)  { // Not extendable (private)
51   *             super(value, name);
52   *         }
53   *         public static final int RED_VALUE = 0xFF0000;
54   *         public static final Color RED = new Color(RED_VALUE, "red");
55   *
56   *         public static final int GREEN_VALUE = 0x00FF00;
57   *         public static final Color GREEN = new Color(GREEN_VALUE, "green");
58   *
59   *         public static final int BLUE_VALUE = 0x0000FF;
60   *         public static final Color BLUE = new Color(BLUE_VALUE, "blue");
61   *     }
62   *     ...
63   *     Color color;
64   *     switch (color.intValue()) {
65   *         case Color.RED_VALUE:
66   *         case Color.GREEN_VALUE:
67   *         case Color.BLUE_VALUE:
68   *     }
69   *     </pre></li>
70   *     <li> Enum's hierarchies may exist, as {@link Enum} are
71   *          not implicitly <code>final</code>. For example:<pre>
72   *     public static class WeekDay extends Enum {
73   *         public static final Collection VALUES = getInstances(WeekDay.class);
74   *         public static final WeekDay SATURDAY = new WeekDay();
75   *         public static final WeekDay SUNDAY = new WeekDay();
76   *     }
77   *     public static class WorkDay extends WeekDay {
78   *         public static final Collection VALUES = getInstances(WorkDay.class);
79   *         public static final WorkDay MONDAY = new WorkDay();
80   *         public static final WorkDay TUESDAY = new WorkDay();
81   *         public static final WorkDay WEDNESDAY = new WorkDay();
82   *         public static final WorkDay THURSDAY = new WorkDay();
83   *         public static final WorkDay FRIDAY = new WorkDay();
84   *     }
85   *     ...
86   *     for (Iterator i = WorkDay.VALUES.iterator(); i.hasNext(); ) {
87   *         // Iterates through the five workdays.
88   *     }       
89   *     for (Iterator i = WeekDay.VALUES.iterator(); i.hasNext(); ) {
90   *         // Iterates through the seven weekdays.
91   *     }</pre></li>
92   *     <li> Finally, this class does not require the latest JDK1.5+</li></ul>
93   *
94   * <p> <b>Limitations:</b> Unlike the JDK1.5 <code>enum</code> base type, 
95   *     instances of this class cannot be used directly in a <code>switch</code>
96   *     statement. Although, for mapped's values (see <code>Color</code> example
97   *     above) a <code>switch</code> on the enum's value is always possible. 
98   *     This approach is also faster as it does not require 
99   *     a "multiway if-statement" or "array indirection" 
100  *     (Ref. <a href=
101  *     "http://jcp.org/aboutJava/communityprocess/jsr/tiger/enum.html">
102  *     JDK 1.5 Enum: IV. Implementation Notes</a>).</p>
103  *
104  * <p><i> This class is <b>public domain</b> (not copyrighted).</i></p>
105  * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
106  * @version 5.3, December 10, 2003
107  */
108 public abstract class Enum extends Number implements Serializable, Comparable {
109     /***
110  * Holds the value-to-enum mappings per class.
111  */
112     private static final FastMap VALUE_ENUM_PER_CLASS = new FastMap();
113 
114     /***
115  * Holds the name-to-enum mappings per class.
116  */
117     private static final FastMap NAME_ENUM_PER_CLASS = new FastMap();
118     private static final LongValue KEY_VALUE = new LongValue(0);
119 
120     /***
121  * Holds the value of this enum.
122  */
123     private transient final long _value;
124 
125     /***
126  * Holds the name of this enum (for anonymous class: "className#ordinal").
127  */
128     private final String _name;
129 
130     /***
131  * Default constructor. The value of this enum is one more than the previous
132  * enum (starting at <code>0</code>). This constructor is typically used for
133  * anonymous extendable enumerations (with value automatically assigned).
134  *
135  * @throws IllegalStateException if an enum with same value already exists.
136  */
137     protected Enum() {
138         this(null, null);
139     }
140 
141     /***
142  * Creates an enumerate of specified name. The value of this enum is
143  * one more than the previous enum (starting at <code>0</code>).
144  * This constructor is typically used for extendable named enumerations
145  * (with value automatically assigned).
146  *
147  * @param  name the name for this enum.
148  * @throws IllegalStateException if an enum with same name or same value
149  *         already exists.
150  */
151     protected Enum(String name) {
152         this(null, name);
153     }
154 
155     /***
156  * Creates an enumerate of specified value. This constructor is typically 
157  * used for non-extendable anonymous enumerations (e.g. masks with value
158  * power of 2).
159  *
160  * @param  value the value for this enum.
161  * @throws IllegalStateException if an enum with same value already exists.
162  */
163     protected Enum(long value) {
164         this(new LongValue(value), null);
165     }
166 
167     /***
168  * Creates an enumerate of specified value and specified name.
169  * This constructor is typically used for enumeration usable in 
170  * <code>switch</code> statements (e.g. state machine) with values 
171  * being defined by <code>public final static int/long</code> constants.
172  *
173  * @param  value the value for this enum.
174  * @param  name the name for this enum.
175  * @throws IllegalStateException if an enum with same name or same value
176  *         already exists.
177  */
178     protected Enum(long value, String name) {
179         this(new LongValue(value), name);
180     }
181 
182     /***
183  * Base constructor.
184  *
185  * @param  value the value for this enum or <code>null</code> if none.
186  * @param  name the name for this enum or <code>null</code> if none.
187  * @throws IllegalStateException if an enum with same name or same value
188  *         already exists.
189  */
190     private Enum(LongValue value, String name) {
191         synchronized (Enum.class) {
192             Class rootClass = this.getRootClass();
193 
194             // Maps value.
195             FastMap values = (FastMap) VALUE_ENUM_PER_CLASS.get(rootClass);
196 
197             if (values == null) {
198                 values = new FastMap();
199                 VALUE_ENUM_PER_CLASS.put(rootClass, values);
200             }
201 
202             if (value == null) { // Value automatically assigned.
203                 value = new LongValue(values.size());
204             }
205 
206             if (values.containsKey(value)) {
207                 throw new IllegalStateException("Value: " + value +
208                     " already assigned");
209             }
210 
211             values.put(value, this);
212 
213             // Adds to intermediate enum collections.
214             for (Class enumClass = getClass(); enumClass != rootClass;
215                     enumClass = enumClass.getSuperclass()) {
216                 values = (FastMap) VALUE_ENUM_PER_CLASS.get(enumClass);
217 
218                 if (values == null) {
219                     values = new FastMap();
220                     VALUE_ENUM_PER_CLASS.put(enumClass, values);
221                 }
222 
223                 values.put(value, this);
224             }
225 
226             // Maps name.
227             FastMap names = (FastMap) NAME_ENUM_PER_CLASS.get(rootClass);
228 
229             if (names == null) {
230                 names = new FastMap();
231                 NAME_ENUM_PER_CLASS.put(rootClass, names);
232             }
233 
234             if (name == null) { // Assigns a unique name per class
235                                 // (used for serialization).
236 
237                 FastMap map = (FastMap) NAME_ENUM_PER_CLASS.get(this.getClass());
238                 int ordinal = (map != null) ? map.size() : 0;
239                 name = this.getClass().getName() + '#' + ordinal;
240             }
241 
242             if (names.containsKey(name)) {
243                 throw new IllegalStateException("Name: " + name +
244                     " already assigned");
245             }
246 
247             names.put(name, this);
248 
249             // Adds to intermediate enum collections.
250             for (Class enumClass = getClass(); enumClass != rootClass;
251                     enumClass = enumClass.getSuperclass()) {
252                 names = (FastMap) NAME_ENUM_PER_CLASS.get(enumClass);
253 
254                 if (names == null) {
255                     names = new FastMap();
256                     NAME_ENUM_PER_CLASS.put(enumClass, names);
257                 }
258 
259                 names.put(name, this);
260             }
261 
262             _value = value._long;
263             _name = name;
264         }
265     }
266 
267     private Class getRootClass() {
268         Class rootClass = this.getClass();
269 
270         while (rootClass.getSuperclass() != Enum.class) {
271             rootClass = rootClass.getSuperclass();
272         }
273 
274         return rootClass;
275     }
276 
277     /***
278  * Compares this enum with the specified object for order.  Returns a
279  * negative integer, zero, or a positive integer as this object is less
280  * than, equal to, or greater than the specified object.
281  *
282  * @param  o the object to be compared.
283  * @return a negative integer, zero, or a positive integer as this object
284  *               is less than, equal to, or greater than the specified object.
285  * @throws ClassCastException if the specified object's type prevents it
286  *         from being compared to this object.
287  */
288     public int compareTo(Object o) {
289         if (this.getRootClass() == ((Enum) o).getRootClass()) {
290             return (((Enum) o)._value == _value) ? 0
291                                                  : ((((Enum) o)._value < _value)
292             ? (-1) : 1);
293         } else {
294             throw new ClassCastException("Cannot compare " + this.getClass() +
295                 " with " + o.getClass());
296         }
297     }
298 
299     /***
300  * Returns a read-only list of the specified enumeration.
301  * The collection returned is backed by the actual collection of enums
302  * -- so it grows as more enums are defined. The iteration order of 
303  * the collection returned is always the declarative order of the 
304  * {@link Enum} regardless of their actual values. This method is 
305  * typically used to export the constant <code>VALUES</code> as in<pre>
306  * public static Color extends Enum {
307  *     public static final Collection VALUES = getInstances(Color.class);
308  *     ... 
309  * }</pre>
310  *
311  * @param  enumClass the enum class.
312  * @return an unmodifiable view of the enum collection.
313  */
314     protected static Collection getInstances(Class enumClass) {
315         synchronized (Enum.class) {
316             FastMap values = (FastMap) VALUE_ENUM_PER_CLASS.get(enumClass);
317 
318             if (values == null) {
319                 values = new FastMap();
320                 VALUE_ENUM_PER_CLASS.put(enumClass, values);
321             }
322 
323             return Collections.unmodifiableCollection(values.values());
324         }
325     }
326 
327     /***
328  * Returns the enum with the specified value. This method also ensures
329  * that the specified class has been initialized.
330  *
331  * @param  value the value of the enum to search for.
332  * @param  enumClass the class of the enum to return.
333  * @return the enum with the specified value or <code>null</code>
334  *         if not found.
335  */
336     public static Enum valueOf(long value, Class enumClass) {
337         synchronized (Enum.class) {
338             ensureInitialization(enumClass);
339 
340             FastMap values = (FastMap) VALUE_ENUM_PER_CLASS.get(enumClass);
341             KEY_VALUE._long = value; // Avoids object creation.
342 
343             return (values != null) ? (Enum) values.get(KEY_VALUE) : null;
344         }
345     }
346 
347     /***
348  * Returns the enum with the specified name. This method also ensures
349  * that the specified class has been initialized.
350  *
351  * @param  name the name of the enum to search for.
352  * @param  enumClass the class of the enum to return.
353  * @return the enum such as <code>name.equals(enum.getName())</code>
354  *         or <code>null</code> if not found.
355  */
356     public static Enum valueOf(CharSequence name, Class enumClass) {
357         synchronized (Enum.class) {
358             ensureInitialization(enumClass);
359 
360             FastMap names = (FastMap) NAME_ENUM_PER_CLASS.get(enumClass);
361 
362             return (names != null) ? (Enum) names.get(name) : null;
363         }
364     }
365 
366     /////////////////////////////
367     // Parent abstract methods //
368     /////////////////////////////
369 
370     /***
371  * Returns this enum's value as a <code>int</code>.
372  * This may involve rounding or truncation.
373  *
374  * @return  the numeric value represented by this enum after conversion
375  *          to type <code>int</code>.
376  */
377     public final int intValue() {
378         return (int) _value;
379     }
380 
381     /***
382  * Returns this enum's value as a <code>long</code>.
383  *
384  * @return  the numeric value represented by this enum after conversion
385  *          to type <code>long</code>.
386  */
387     public final long longValue() {
388         return _value;
389     }
390 
391     /***
392  * Returns this enum's value as a <code>float</code>.
393  * This may involve rounding.
394  *
395  * @return  the numeric value represented by this enum after conversion
396  *          to type <code>float</code>.
397  */
398     public final float floatValue() {
399         return _value;
400     }
401 
402     /***
403  * Returns this enum's value as a <code>double</code>.
404  * This may involve rounding.
405  *
406  * @return the numeric value represented by this enum after conversion
407  *         to type <code>double</code>.
408  */
409     public final double doubleValue() {
410         return _value;
411     }
412 
413     /***
414  * Returns the unique name for this enum. For anonymous enum, 
415  * the name is constructed from the enum class and the declarative 
416  * order of the enum <b>within</b> its class (e.g. <code>Color#0</code>
417  * for the first declared color in the <code>Color</code> class).
418  *
419  * @return the name specified at construction or an unique name derived  
420  *         from the enum class name and its declarative order.
421  */
422     public final String getName() {
423         return _name;
424     }
425 
426     /***
427  * Returns a string representation of this enum.
428  *
429  * @return {@link #getName()}
430  */
431     public String toString() {
432         return _name;
433     }
434 
435     /***
436  * Overrides <code>readResolve()</code> to support
437  * <code>Serialization</code>. Enum are uniquely identified by their
438  * name (independantly of their values to avoid class loading dependencies). 
439  *
440  * @return  the unique enum identified by the serialized name.
441  * @throws  ObjectStreamException if the enum is not found.
442  * @see     #getName()
443  */
444     protected Object readResolve() throws ObjectStreamException {
445         // Searches using unique name.
446         Enum enum = valueOf(this._name, this.getClass());
447 
448         if (enum != null) {
449             return enum;
450         } else { // Not found.
451             throw new InvalidObjectException("Enum: " + this + " not found");
452         }
453     }
454 
455     /***
456  * Throws CloneNotSupportedException.  This guarantees that enums
457  * are never cloned, which is necessary to preserve their "singleton"
458  * status.
459  *
460  * @return (never returns)
461  */
462     protected final Object clone() throws CloneNotSupportedException {
463         throw new CloneNotSupportedException("Enum cannot be cloned");
464     }
465 
466     /***
467  * Ensures initialization of the specified enum class.
468  * 
469  * @param  enumClass the class to ensure the initialization. 
470  */
471     private static void ensureInitialization(Class enumClass) {
472         if (!VALUE_ENUM_PER_CLASS.containsKey(enumClass)) {
473             // Unfortunately, there is no easy way to force class 
474             // initialization.
475             try {
476                 Class.forName(enumClass.getName());
477             } catch (ClassNotFoundException e) {
478                 throw new InternalError(); // Should never get there.
479             }
480         }
481     }
482 
483     /***
484  * This class represents a long value for value-to-enum mappings. 
485  */
486     private static final class LongValue {
487         long _long;
488 
489         LongValue(long value) {
490             _long = value;
491         }
492 
493         public boolean equals(Object that) {
494             return (this._long == ((LongValue) that)._long);
495         }
496 
497         public int hashCode() {
498             return (int) _long;
499         }
500     }
501 }